import strutils

# Helpers
proc shift*[T](s: var seq[T]): T {.inline, noSideEffect.} =
  ## Remove and return the first element of a seq
  result = s[0]
  s.delete(0)

# Gap buffer
type
  GapBuffer* = object of RootObj
    head*, tail*: seq[char]

proc newGapBuffer*: GapBuffer =
  GapBuffer(
    head: @[],
    tail: @[]
  )

proc toGapBuffer*(str: string, position = -1): GapBuffer =
  ## Creates a `GapBuffer` from a `string`
  var
    buf: seq[char] = @[]
    lastChar = str.len - 1

  for i in 0..lastChar: buf.add(str[i])

  var head, tail: seq[char] = @[]
  if position >= 0:
    head = buf[0..position]
  if position < lastChar:
    tail = buf[position+1..lastChar-1]

  GapBuffer(
    head: head,
    tail: tail
  )

proc `$`*(buf: GapBuffer): string {.noSideEffect.} =
  ## The stringify operator for a GapBuffer argument. Returns `x`
  ## converted to a string.

  buf.head.join() & buf.tail.join()

proc len*(buf: GapBuffer): int {.noSideEffect.} =
  ## Returns the length of a GapBuffer.
  buf.head.len + buf.tail.len

proc pos*(buf: GapBuffer): int {.noSideEffect.} =
  ## Returns the position of the gap
  buf.head.len - 1

proc move*(buf: var GapBuffer, amt: int) =
  ## Move the gap.
  if amt < 0:
    for _ in 1..(-amt):
      buf.tail.insert(buf.head.pop(), 0)
  else:
    for _ in 1..amt:
      buf.head.add(buf.tail.shift())

proc moveTo*(buf: var GapBuffer, pos: int) =
  ## Move the gap to an absolute position
  var headLen = buf.head.len
  if pos < headLen:
    var newHead, newTail: seq[char] = @[]
    for i in 0..pos:
      newHead.add(buf.head[i])

    for i in pos+1..headLen-1:
      newTail.add(buf.head[i])
    newTail &= buf.tail

    buf.head = newHead
    buf.tail = newTail

  elif pos > headLen:
    var tailPos = pos - headLen
    for i in 0..tailPos:
      buf.head.add(buf.tail[i])

    var newTail: seq[char] = @[]
    for i in tailPos+1..buf.tail.len-1:
      newTail.add(buf.tail[i])

    buf.tail = newTail

proc insert*(buf: var GapBuffer, ch: char) =
  ## Insert a character before the gap
  buf.head.add(ch)

proc insert*(buf: var GapBuffer, str: string) =
  ## Insert a string before the gap
  for ch in str.items():
    buf.insert(ch)

proc delete*(buf: var GapBuffer) =
  ## Remove the character before the gap
  buf.head.setLen(buf.head.len - 1)

proc `[]`*(buf: GapBuffer, i: int): char {.noSideEffect.} =
  ## Get the character at the index `i`
  var headLen = buf.head.len
  if i < headLen:
    buf.head[i]
  else:
    buf.tail[i - headLen]

proc `[]=`*(buf: var GapBuffer, i: int, c: char) =
  ## Set ther character at the index `i`
  var headLen = buf.head.len
  if i < headLen:
    buf.head[i] = c
  else:
    buf.tail[i - headLen] = c

# Iterators
iterator items*(buf: GapBuffer): char =
  for c in buf.head: yield c
  for c in buf.tail: yield c

iterator splitLines*(buf: GapBuffer): seq[char] =
  ## Splits a GapBuffer into lines.
  template impl(s: seq[char]): untyped =
    for i in 0..s.len-1:
      case s[i]:
        of '\l':
          yield line
          line = @[]

        of '\c':
          if i+1 < buf.len and s[i+1] != '\l':
            # If it's not a line feed, treat this as the end of the line,
            # if it is, let the line feed case handle it on the next
            # character.
            yield line
            line = @[]

        else:
          line.add(s[i])


  var line: seq[char] = @[]
  impl(buf.head)
  impl(buf.tail)
  yield line

